SQL 資料庫的寫入,必須要加上鎖 (lock) 才能確保一致性。
參考下方的示意圖,A 與 B 是兩條執行緒,如果它們同時對同一筆資料做更新的動作,很有可能會造成錯誤。理論上,A 與 B 都成功完成的結果應該要得到 3
。實際上,如果兩個操作沒有充分地隔離而互相影響,就有可能得到結果為 1
或是 2
。
[table X 的 i row j column 的初始值為 0]
A -------> [對 table X 的 i row j column + 1]
B -------> [對 table X 的 i row j column + 2]
-------------------> 時間軸
這個加鎖的開銷非常高,也有統計指出,在高併發的條件之下,資料庫的效能有很大一部分用於鎖定資料表。
因為加鎖的開銷高的關系,SQL 資料庫預設的選項通常是提供樂觀鎖 (optimistic lock) ,也就是,能不要加鎖就不加鎖,少數的寫入失敗交給資料庫使用者去自行處理。這種機制一方面提高了效能,另一方面,又讓隔離沒有辦法做到 100% ,所以與資料互動的高併發程式,往往會充滿了各式各樣的奇技淫巧。
id
這個欄位,要下什麼查詢,可以找出所有這些表?這一類的問題,在 Postgres 還算是有不錯的權宜之計,查詢「系統資料表」即可。
SELECT table_schema, table_name
FROM information_schema.columns
WHERE column_name = 'id'
ORDER BY table_schema, table_name;
在 SQL 資料庫,資料表綱要 (schema) 本身並不是資料,而是指令。然而,在實務的工作之中,我們往往都還是會在某個關鍵時刻需要將資料表綱要視為資料來做查詢。
SQL 的使用者如果一開始不知道多數的廠商通常都會設計自家的「系統資料表」時,往往會在這個部分覺得非常卡關。
如果有一張表 A
,裡頭有三個欄位:
;; 表 A
id, item_name, price
總是偶爾會發生一種情況,我們很想要在表 A
裡,放入第四個欄位 has_discount
,且有一些列 (row) 會有第四個欄位,有一些則會沒有。
這時通常有兩種作法:
A
的資料表綱要 (schema) ,增加第四個欄位,且容許 NULL 值。B
,讓它包含第四個欄位,並且讓它指向表 A
,日後需要第四欄位時,就用 join 來處理。;; 表 B
A_id, has_discount
這兩種作法都有小小缺點:
NULL
,因為 NULL
在此處的概念是『沒有』。然而,很多資料庫的教材,都會講 NULL
也可以用來表示不合理的值,也因此,一旦使用了 NULL
值,就開始讓資料庫的語意顯得有點不清晰。阻抗不匹配一詞來自於電子工程,指的是兩種不同電子系統在相互作用時出現的阻抗不相容。後來,這個詞彙被引伸,用來描述在物件導向程式設計 (OOP) 與 SQL 資料庫的場景中,物件導向模型和關聯式資料模型之間的概念差異,因而導致的開發困難。
不匹配有許多形式,其中一種是 N+1 問題。
什麼是 N+1 問題呢?應用軟體工程師若使用 Ruby on Rails 之類的開發框架來輔助產生 SQL 來取代手刻 SQL,由於 SQL 是由 Ruby on Rails 自動生成的,它預設不會刻意去做效能的最佳化,這時就會有 N+1 問題發生。舉例來講,如果要做的操作是從學校 (school) 這個資料庫裡,取出某個班級 (class) 的所有學生 (student) 的資料,並且對每個學生的數學成績做開根號乘以十。自動生成的 SQL 有可能會是這樣子做:
一旦考慮到 IO 非常慢的這個事實之後,上述自動生成的 N+1 個 SQL 自然是相當地低效率。高效的作法,則是只產生一個 SQL 查詢,它內含了 join ,所以可以只用一個 SQL 查詢,就做完步驟1與步驟 2 的工作,即只用一個 SQL 查詢就取回所有學生的數學成績,而這種作法才是符合 SQL 資料庫概念模型的作法。
然而,嚴格來說,上述的 N+1 問題如果只是效能差一些,其實是可以忽略不計。真正不能忍受的錯誤其實是在資料的一致性上。由於產生了 N+1 個 SQL 查詢而非 1 個,這 N+1 個 SQL 查詢自然是在不同的時間點運行,如果中途資料發生了修改,那就會造成資料的不一致,進而造成邏輯錯誤。
其它資源: